<!DOCTYPE html> <!-- -*- html -*- -->
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1">
  <title>Implicit Function of Best Fit</title>
  <link rel="shortcut icon" href="img/gatech.gif"/>

  
      <link rel="stylesheet" href="css/demo.css?vers=2759ff">
  

  <style>
      

html, body {
    margin:           0;
    height:           100%;
    background-color: white;
    overflow-x:       hidden;
}
.mathbox-wrapper {
    width:       50%;
    padding-top: 50%;
    position:    absolute;
    left:        0;
    top:         50%;
    transform:   translate(0, -50%);
}
.mathbox-wrapper + .mathbox-wrapper {
    left:        50%;
}
.mathbox-wrapper > div {
    position: absolute;
    top:      0;
    left:     0;
    width:    100%;
    height:   100%;
}
.mathbox-label {
    position:  absolute;
    left:      50%;
    top:       10px;
    color:     black;
    opacity:   1.0;
    background-color: rgba(220, 220, 220, .5);
    border:    solid 1px rgba(50, 50, 50, .5);
    padding:   5px;
    transform: translate(-50%, 0);
}
.overlay-text {
    z-index: 1;
}

  #eqn-here {
    color: var(--palette-red);
  }
  #sumsq-here {
    color: var(--palette-violet);
  }

  </style>

</head>
<body>
    

<div class="overlay-text">
  <p>Best-fit equation: <span id="eqn-here"></span></p>
  <p>Quantity minimized: <span id="sumsq-here"></span></p>
</div>

<div class="mathbox-wrapper">
    <div id="mathbox1">
        
<div class="mathbox-label">Graph</div>

    </div>
</div>
<div class="mathbox-wrapper">
    <div id="mathbox2">
        
<div class="mathbox-label">Zero Set</div>

    </div>
</div>


    
        <script src="js/demo.js?vers=77646a"></script>
    

    <script type="text/javascript">
        "use strict";
        DomReady.ready(function() {

        var bestFitStr, bestfit, bvec, c, clipFragment, clipShader, curveFragment, dot, func, funcFragment, funcStr, funcStrUrl, i, k, l, len, len1, len2, letter, m, makeString, matrix, n, numParams, numSamples, numTargets, o, obj, op, p, param, paramVals, params, radius, range, rangeZ, ref, ref1, ref2, ref3, ref4, s, samples, shadeFragment, solve, target, targets, uniforms, units, updateCaption, vars, xhat, zeroParams;

range = urlParams.get('range', 'float', 10);

rangeZ = urlParams.get('rangez', 'float', range);

funcStr = (ref = urlParams.func) != null ? ref : 'C*x+D';

funcStr = funcStr.replace(/\s+/g, '+');

func = exprEval.Parser.parse(funcStr);

params = [];

zeroParams = {};

uniforms = {};

vars = ['x', 'y'];

ref1 = func.variables().sort();
for (k = 0, len = ref1.length; k < len; k++) {
  letter = ref1[k];
  if (letter === 'x' || letter === 'y') {
    continue;
  }
  params.push(letter);
  zeroParams[letter] = 0;
  uniforms[letter] = {
    type: 'f',
    value: 0.0
  };
}

numParams = params.length;

units = [];

for (i = l = 0, ref2 = numParams; 0 <= ref2 ? l < ref2 : l > ref2; i = 0 <= ref2 ? ++l : --l) {
  obj = {};
  for (m = 0, len1 = params.length; m < len1; m++) {
    param = params[m];
    obj[param] = 0;
  }
  obj[params[i]] = 1;
  units.push(obj);
}

targets = [];

i = 1;

while (urlParams["v" + i] != null) {
  target = urlParams.get("v" + i, 'float[]');
  target[2] = 0;
  targets.push(target);
  i++;
}

numTargets = targets.length;

matrix = (function() {
  var n, ref3, results;
  results = [];
  for (n = 0, ref3 = numParams; 0 <= ref3 ? n < ref3 : n > ref3; 0 <= ref3 ? n++ : n--) {
    results.push((function() {
      var o, ref4, results1;
      results1 = [];
      for (o = 0, ref4 = numTargets; 0 <= ref4 ? o < ref4 : o > ref4; 0 <= ref4 ? o++ : o--) {
        results1.push(0);
      }
      return results1;
    })());
  }
  return results;
})();

bvec = (function() {
  var n, ref3, results;
  results = [];
  for (n = 0, ref3 = numTargets; 0 <= ref3 ? n < ref3 : n > ref3; 0 <= ref3 ? n++ : n--) {
    results.push(0);
  }
  return results;
})();

xhat = (function() {
  var n, ref3, results;
  results = [];
  for (n = 0, ref3 = numParams; 0 <= ref3 ? n < ref3 : n > ref3; 0 <= ref3 ? n++ : n--) {
    results.push(0);
  }
  return results;
})();

bestfit = function(x, y) {
  return 0;
};

bestFitStr = '';

paramVals = {};

funcStrUrl = funcStr.replace(/([a-zA-Z_]+)\^(\d+)/g, function(match, p1, p2) {
  return ((function() {
    var n, ref3, results;
    results = [];
    for (n = 0, ref3 = parseInt(p2); 0 <= ref3 ? n < ref3 : n > ref3; 0 <= ref3 ? n++ : n--) {
      results.push(p1);
    }
    return results;
  })()).join("*");
});

funcFragment = '';

for (n = 0, len2 = params.length; n < len2; n++) {
  letter = params[n];
  funcFragment += "uniform float " + letter + ";\n";
}

funcFragment += "\n\nfloat func(float x, float y) {\n    return " + funcStrUrl + ";\n}";

for (op in func.unaryOps) {
  if (op.match(/^[a-zA-Z]+$/)) {
    funcStr = funcStr.replace(new RegExp(op, 'g'), "\\" + op);
  }
}

dot = function(v1, v2) {
  var o, ref3, ret;
  ret = 0;
  for (i = o = 0, ref3 = v1.length; 0 <= ref3 ? o < ref3 : o > ref3; i = 0 <= ref3 ? ++o : --o) {
    ret += v1[i] * v2[i];
  }
  return ret;
};

updateCaption = function() {};

solve = function() {
  var ATA, ATb, constant, eqno, j, len3, linear, o, p, q, ref3, ref4, solver;
  for (eqno = o = 0, ref3 = numTargets; 0 <= ref3 ? o < ref3 : o > ref3; eqno = 0 <= ref3 ? ++o : --o) {
    target = targets[eqno];
    linear = func.simplify({
      x: target[0],
      y: target[1]
    });
    constant = linear.evaluate(zeroParams);
    for (i = p = 0, ref4 = numParams; 0 <= ref4 ? p < ref4 : p > ref4; i = 0 <= ref4 ? ++p : --p) {
      matrix[i][eqno] = linear.evaluate(units[i]) - constant;
    }
    bvec[eqno] = target[2] - constant;
  }
  ATA = (function() {
    var q, ref5, results;
    results = [];
    for (j = q = 0, ref5 = numParams; 0 <= ref5 ? q < ref5 : q > ref5; j = 0 <= ref5 ? ++q : --q) {
      results.push((function() {
        var r, ref6, results1;
        results1 = [];
        for (i = r = 0, ref6 = numParams; 0 <= ref6 ? r < ref6 : r > ref6; i = 0 <= ref6 ? ++r : --r) {
          results1.push(dot(matrix[i], matrix[j]));
        }
        return results1;
      })());
    }
    return results;
  })();
  ATb = (function() {
    var q, ref5, results;
    results = [];
    for (i = q = 0, ref5 = numParams; 0 <= ref5 ? q < ref5 : q > ref5; i = 0 <= ref5 ? ++q : --q) {
      results.push(dot(matrix[i], bvec));
    }
    return results;
  })();
  solver = rowReduce(ATA)[3];
  solver(ATb, xhat);
  for (i = q = 0, len3 = params.length; q < len3; i = ++q) {
    letter = params[i];
    paramVals[letter] = xhat[i];
    uniforms[letter].value = xhat[i];
  }
  bestfit = func.simplify(paramVals).toJSFunction(vars.join(','));
  makeString();
  return updateCaption();
};

makeString = function() {
  var len3, o, results, val, valAlone, valMinus, valPlus;
  bestFitStr = funcStr;
  results = [];
  for (i = o = 0, len3 = params.length; o < len3; i = ++o) {
    letter = params[i];
    val = xhat[i];
    if (val >= 0) {
      valAlone = val.toFixed(2);
      valPlus = "+" + valAlone;
      valMinus = "-" + valAlone;
    }
    if (val < 0) {
      val = -val;
      valAlone = val.toFixed(2);
      valPlus = "-" + valAlone;
      valMinus = "+" + valAlone;
      valAlone = "-" + valAlone;
    }
    bestFitStr = bestFitStr.replace(new RegExp("\\+" + letter + "\\*", 'g'), valPlus + '\\,');
    bestFitStr = bestFitStr.replace(new RegExp("\\+" + letter, 'g'), valPlus);
    bestFitStr = bestFitStr.replace(new RegExp("-" + letter + "\\*", 'g'), valMinus + '\\,');
    bestFitStr = bestFitStr.replace(new RegExp("-" + letter, 'g'), valMinus);
    bestFitStr = bestFitStr.replace(new RegExp(letter + "\\*", 'g'), valAlone + '\\,');
    bestFitStr = bestFitStr.replace(new RegExp("" + letter, 'g'), valAlone);
    results.push(bestFitStr = bestFitStr.replace(/\*/g, ''));
  }
  return results;
};

solve();

clipShader = "// Enable STPQ mapping\n#define POSITION_STPQ\nvoid getPosition(inout vec4 xyzw, inout vec4 stpq) {\n  // Store XYZ per vertex in STPQ\nstpq = xyzw;\n}";

shadeFragment = "varying vec3 vNormal;\nvarying vec3 vLight;\nvarying vec3 vPosition;\n\nvec3 offSpecular(vec3 color) {\n  vec3 c = 1.0 - color;\n  return 1.0 - c * c;\n}\n\nvec4 getShadedColor(vec4 rgba) {\n\n  vec3 color = rgba.xyz;\n  vec3 color2 = offSpecular(rgba.xyz);\n\n  vec3 normal = normalize(vNormal);\n  vec3 light = normalize(vLight);\n  vec3 position = normalize(vPosition);\n\n  float side    = gl_FrontFacing ? -1.0 : 1.0;\n  float cosine  = side * dot(normal, light);\n  float diffuse = mix(max(0.0, cosine), .5 + .5 * cosine, .1);\n\n  vec3  halfLight = normalize(light + position);\n	float cosineHalf = max(0.0, side * dot(normal, halfLight));\n	float specular = pow(cosineHalf, 16.0);\n\n	return vec4(color * (diffuse * .9 + .05) + .25 * color2 * specular, rgba.a);\n}";

numSamples = 8;

radius = 0.02;

samples = '';

for (i = o = 0, ref3 = numSamples; 0 <= ref3 ? o < ref3 : o > ref3; i = 0 <= ref3 ? ++o : --o) {
  c = Math.cos(2 * π * i / numSamples) * radius;
  s = Math.sin(2 * π * i / numSamples) * radius;
  samples += "if(func(stpq.x + " + (c.toFixed(8)) + ", stpq.y + " + (s.toFixed(8)) + ") * val < 0.0)\n";
  samples += "    return rgba;\n";
}

curveFragment = "// Enable STPQ mapping\n#define POSITION_STPQ\n\nvec4 getColor(vec4 rgba, inout vec4 stpq) {\n    float val = func(stpq.x, stpq.y);\n\n    " + samples + "\n\n    discard;\n}";

samples = '';

radius = 0.1;

for (i = p = 0, ref4 = numSamples; 0 <= ref4 ? p < ref4 : p > ref4; i = 0 <= ref4 ? ++p : --p) {
  c = Math.cos(2 * π * i / numSamples) * radius;
  s = Math.sin(2 * π * i / numSamples) * radius;
  samples += "if(func(stpq.x + " + (c.toFixed(8)) + ", stpq.y + " + (s.toFixed(8)) + ") * val < 0.0)\n";
  samples += "    return rgba;\n";
}

clipFragment = "// Enable STPQ mapping\n#define POSITION_STPQ\nuniform float range;\nuniform float rangeZ;\nuniform int hilite;\n\nvec4 getColor(vec4 rgba, inout vec4 stpq) {\n    float val = stpq.z;\n    rgba = getShadedColor(rgba);\n    vec4 oldrgba = rgba;\n\n    // Discard pixels outside of clip box\n    if(abs(stpq.x) > range || abs(stpq.y) > range || abs(stpq.z) > rangeZ)\n        discard;\n\n    rgba.xyz *= 10.0;\n    rgba.w = 1.0;\n\n    if(hilite != 0 && stpq.z < " + radius + " && stpq.z > -" + radius + ") {\n        " + samples + "\n    }\n\n    return oldrgba;\n}";

window.demo1 = new Demo({
  mathbox: {
    element: document.getElementById("mathbox1")
  },
  scaleUI: true,
  camera: {
    position: urlParams.get('camera1', 'float[]', [3, 1.5, 1.5])
  }
}, function() {
  var clipCube, cube, geo, mesh, view;
  window.mathbox1 = this.mathbox;
  view = this.view({
    axes: false,
    grid: true,
    viewRange: [[-range, range], [-range, range], [-rangeZ, rangeZ]]
  });
  this.labeledPoints(view, {
    name: 'targets',
    points: targets,
    colors: (function() {
      var q, ref5, results;
      results = [];
      for (q = 0, ref5 = numTargets; 0 <= ref5 ? q < ref5 : q > ref5; 0 <= ref5 ? q++ : q--) {
        results.push(new Color("blue"));
      }
      return results;
    })(),
    live: true,
    pointOpts: {
      zIndex: 2
    }
  });
  uniforms.range = {
    type: 'f',
    value: range
  };
  uniforms.rangeZ = {
    type: 'f',
    value: rangeZ
  };
  uniforms.hilite = {
    type: 'i',
    value: 1
  };
  clipCube = view.shader({
    code: clipShader
  }).vertex({
    pass: 'data'
  }).shader({
    code: funcFragment + "\n" + shadeFragment + "\n" + clipFragment,
    uniforms: uniforms
  }).fragment();
  geo = new THREE.BoxGeometry(2, 2, 2);
  mesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial());
  cube = new THREE.BoxHelper(mesh);
  cube.material.color = new THREE.Color(.7, .7, .7);
  this.three.scene.add(cube);
  clipCube.area({
    channels: 3,
    rangeX: [-range, range],
    rangeY: [-range, range],
    width: 100,
    height: 100,
    expr: function(emit, x, y) {
      return emit(x, y, bestfit(x, y));
    }
  }).surface({
    color: new Color("red").arr(),
    opacity: 0.7,
    fill: true
  });
  return view.array({
    channels: 3,
    width: 2,
    items: numTargets,
    expr: function(emit, end) {
      var q, ref5, results, x, y, z;
      results = [];
      for (i = q = 0, ref5 = numTargets; 0 <= ref5 ? q < ref5 : q > ref5; i = 0 <= ref5 ? ++q : --q) {
        x = targets[i][0];
        y = targets[i][1];
        z = targets[i][2];
        if (end) {
          results.push(emit(x, y, bestfit(x, y)));
        } else {
          results.push(emit(x, y, z));
        }
      }
      return results;
    }
  }).line({
    color: new Color("violet").arr(),
    width: 3,
    zIndex: 3
  });
});

window.demo2 = new Demo2D({
  mathbox: {
    element: document.getElementById("mathbox2")
  },
  scaleUI: true
}, function() {
  var bestFitElt, curve, minimElt, view;
  window.mathbox2 = this.mathbox;
  view = this.view({
    axes: true,
    axisLabels: false,
    grid: true,
    viewRange: [[-range, range], [-range, range]]
  });
  this.labeledPoints(view, {
    name: 'targets',
    points: targets,
    colors: (function() {
      var q, ref5, results;
      results = [];
      for (q = 0, ref5 = numTargets; 0 <= ref5 ? q < ref5 : q > ref5; 0 <= ref5 ? q++ : q--) {
        results.push(new Color("blue"));
      }
      return results;
    })(),
    live: true,
    pointOpts: {
      zIndex: 2
    }
  });
  curve = view.shader({
    code: clipShader
  }).vertex({
    pass: 'data'
  }).shader({
    code: funcFragment + "\n" + curveFragment,
    uniforms: uniforms
  }).fragment();
  curve.matrix({
    channels: 2,
    width: 2,
    height: 2,
    data: [[[-range, -range], [-range, range]], [[range, -range], [range, range]]]
  }).surface({
    color: new Color("red").arr(),
    opacity: 1.0,
    fill: true
  });
  this.draggable(view, {
    points: targets,
    postDrag: solve
  });
  bestFitElt = document.getElementById('eqn-here');
  minimElt = document.getElementById('sumsq-here');
  updateCaption = (function(_this) {
    return function() {
      var diff, len3, minimized, q, quantity, str;
      katex.render("\\quad 0 = " + bestFitStr, bestFitElt);
      minimized = [];
      quantity = 0;
      for (q = 0, len3 = targets.length; q < len3; q++) {
        target = targets[q];
        diff = Math.abs(target[2] - bestfit(target[0], target[1]));
        minimized.push((diff.toFixed(2)) + "^2");
        quantity += diff * diff;
      }
      str = '\\quad' + quantity.toFixed(2) + '=' + minimized.join('+');
      return katex.render(str, minimElt);
    };
  })(this);
  return updateCaption();
});


        });
    </script>
</body>
</html>

